package com.example.demo.redis.aop;

import com.example.demo.redis.RedisKey;
import com.example.demo.redis.annotation.RedisCache;
import com.example.demo.utils.AspectUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Objects;
import java.util.Optional;

/**
 * @author zch
 * 用于解析RedisCache注解的Aspect，使用redis做缓存
 */
@Aspect
@Component
public class RedisCacheAspect {

    private final RedisTemplate<String, Object> redisTemplate;

    public RedisCacheAspect(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    @SuppressWarnings("EmptyMethod")
    @Pointcut("@annotation(com.example.demo.redis.annotation.RedisCache)")
    public void RedisCachePointcut() {}

    /**
     * 用于实现RedisCache注解的功能，存储函数返回值到redis中
     */
    @Around("RedisCachePointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {

        String typeName = joinPoint.getSignature().getDeclaringTypeName();
        String methodName = joinPoint.getSignature().getName();
        String argString = Arrays.asList(joinPoint.getArgs()).toString();
        Method method = AspectUtil.getMethodByJoinPoint(joinPoint);
        RedisCache redisCacheAnnotation = Objects.requireNonNull(method).getAnnotation(RedisCache.class);
        Class<?> returnType = method.getReturnType();

        if(returnType == Void.class)
            return joinPoint.proceed();

        String key = RedisKey.REDIS_CACHE.replace("{methodName}",
                typeName + "." + methodName + argString);
        Optional<Object> value;

        if(redisCacheAnnotation.enable()) {
            value = Optional.ofNullable(redisTemplate.opsForValue().get(key));
            if (value.isPresent()) {
                return value.get();
            }
            value = Optional.ofNullable(joinPoint.proceed());
            if (value.isPresent()) {
                redisTemplate.opsForValue().set(key, value.get(), redisCacheAnnotation.expireTime(),
                        redisCacheAnnotation.expireTimeUnit());
                return value.get();
            } else {
                return null;
            }
        } else {
            return joinPoint.proceed();
        }
    }

}
